import {Matrix3} from '../../math/Matrix3.js';
import {Plane} from '../../math/Plane.js';

function WebGLClipping(properties) {

  const scope = this;

  let globalState = null,
    numGlobalPlanes = 0,
    localClippingEnabled = false,
    renderingShadows = false;

  const plane = new Plane(),
    viewNormalMatrix = new Matrix3(),

    uniform = {value: null, needsUpdate: false};

  this.uniform = uniform;
  this.numPlanes = 0;
  this.numIntersection = 0;

  this.init = function (planes, enableLocalClipping, camera) {

    const enabled =
      planes.length !== 0 ||
      enableLocalClipping ||
      // enable state of previous frame - the clipping code has to
      // run another frame in order to reset the state:
      numGlobalPlanes !== 0 ||
      localClippingEnabled;

    localClippingEnabled = enableLocalClipping;

    globalState = projectPlanes(planes, camera, 0);
    numGlobalPlanes = planes.length;

    return enabled;

  };

  this.beginShadows = function () {

    renderingShadows = true;
    projectPlanes(null);

  };

  this.endShadows = function () {

    renderingShadows = false;
    resetGlobalState();

  };

  this.setState = function (material, camera, useCache) {

    const planes = material.clippingPlanes,
      clipIntersection = material.clipIntersection,
      clipShadows = material.clipShadows;

    const materialProperties = properties.get(material);

    if (!localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && !clipShadows) {

      // there's no local clipping

      if (renderingShadows) {

        // there's no global clipping

        projectPlanes(null);

      } else {

        resetGlobalState();

      }

    } else {

      const nGlobal = renderingShadows ? 0 : numGlobalPlanes,
        lGlobal = nGlobal * 4;

      let dstArray = materialProperties.clippingState || null;

      uniform.value = dstArray; // ensure unique state

      dstArray = projectPlanes(planes, camera, lGlobal, useCache);

      for (let i = 0; i !== lGlobal; ++i) {

        dstArray[i] = globalState[i];

      }

      materialProperties.clippingState = dstArray;
      this.numIntersection = clipIntersection ? this.numPlanes : 0;
      this.numPlanes += nGlobal;

    }


  };

  function resetGlobalState() {

    if (uniform.value !== globalState) {

      uniform.value = globalState;
      uniform.needsUpdate = numGlobalPlanes > 0;

    }

    scope.numPlanes = numGlobalPlanes;
    scope.numIntersection = 0;

  }

  function projectPlanes(planes, camera, dstOffset, skipTransform) {

    const nPlanes = planes !== null ? planes.length : 0;
    let dstArray = null;

    if (nPlanes !== 0) {

      dstArray = uniform.value;

      if (skipTransform !== true || dstArray === null) {

        const flatSize = dstOffset + nPlanes * 4,
          viewMatrix = camera.matrixWorldInverse;

        viewNormalMatrix.getNormalMatrix(viewMatrix);

        if (dstArray === null || dstArray.length < flatSize) {

          dstArray = new Float32Array(flatSize);

        }

        for (let i = 0, i4 = dstOffset; i !== nPlanes; ++i, i4 += 4) {

          plane.copy(planes[i]).applyMatrix4(viewMatrix, viewNormalMatrix);

          plane.normal.toArray(dstArray, i4);
          dstArray[i4 + 3] = plane.constant;

        }

      }

      uniform.value = dstArray;
      uniform.needsUpdate = true;

    }

    scope.numPlanes = nPlanes;
    scope.numIntersection = 0;

    return dstArray;

  }

}


export {WebGLClipping};
